package com.xf.system.common.facade;

import com.xf.system.common.model.BaseModel;
import com.xf.system.common.model.Menu;
import com.xf.system.common.model.Role;
import com.xf.system.common.service.MenuService;
import com.xf.system.common.service.RoleMenuService;
import com.xf.system.common.service.RoleService;
import com.xf.system.constants.BaseConst;
import com.xf.system.enums.StatusEnum;
import com.xf.system.exception.BusinessException;
import com.xf.system.exception.Errors;
import com.xf.system.http.req.menu.MenuAddReqDTO;
import com.xf.system.http.req.menu.MenuListReqDTO;
import com.xf.system.http.req.menu.MenuUpdateReqDTO;
import com.xf.system.http.vo.menu.MenuTreeVO;
import com.xf.system.http.vo.menu.MenuVO;
import com.xf.system.utils.StringUtil.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @author xiefengf
 */
@Service
@Slf4j
public class MenuFacade {

    @Resource
    private MenuService menuService;

    @Resource
    private RoleService roleService;

    @Resource
    private RoleMenuService roleMenuService;

    /**
     * 当前菜单列表树形查询
     *
     * @param reqDto 分页信息+查询条件
     * @return 菜单树集合
     */
    public MenuVO listMenuTree(MenuListReqDTO reqDto) {
        // 1、条件查询所有菜单数据
        List<Menu> menuList = menuService.selectMenuList(reqDto);
        // 2、生成树形结构
        List<MenuTreeVO> menuTreeVoList = new ArrayList<>();
        // 2.1 获取当前所有菜单的主键id
        List<Long> idList = menuList.stream().map(BaseModel::getId).collect(Collectors.toList());
        idList.add(0, BaseConst.TOP_LEVEL_NODE_ID);
        List<Long> menuIdList = new ArrayList<>(idList);
        // 2.3 按照父主键id分组
        Map<Long, List<Menu>> listMap = menuList.stream().collect(Collectors.groupingBy(Menu::getParentId));
        // 2.2、根据顶级节点生成树形结构 --- 没有查询条件就只有一棵树、有查询条件可能有多颗树
        if (reqDto.getMenuName() == null || "".equals(reqDto.getMenuName())){
            List<MenuTreeVO> treeNode1 = getMenuTree(listMap,BaseConst.TOP_LEVEL_NODE_ID,menuIdList);
            menuTreeVoList.addAll(treeNode1);
        }else {
            // 2.3 筛选并获取所有逻辑父节点的菜单树 --- 此段代码为了条件查询数据而实现
            for (Menu menu : menuList) {
                // 如果主键集合中不包含当前节点的父节点，则代表当前节点就为逻辑父节点
                if (!idList.contains(menu.getParentId())) {
                    List<MenuTreeVO> treeNode2 = getMenuTree(listMap, menu.getParentId(),menuIdList);
                    // 2.4 添加至集合中
                    menuTreeVoList.addAll(treeNode2);
                }
            }
        }
        // 3、封装数据
        MenuVO allVo = new MenuVO();
        allVo.setMenuTreeVoList(menuTreeVoList);
        return allVo;
    }

    /**
     * 当前角色对应菜单树
     *
     * @param id 角色id
     * @return 菜单树集合
     */
    public MenuVO listRoleMenuTree(Long id){
        // 1、校验角色是否存在（根据角色id查询角色信息）
        Role role = roleService.getById(id);
        if (StringUtils.isNull(role)){
            // 当前角色不存在
            throw new BusinessException(Errors.System.SYSTEM_01016);
        }
        // 2、查询对应角色的所有菜单数据
        List<Menu> menuList = menuService.selectMenuList(new MenuListReqDTO());
        // 2.1 按照父主键id分组
        Map<Long, List<Menu>> listMap = menuList.stream().collect(Collectors.groupingBy(Menu::getParentId));
        // 3、查询当前角色对应的所有菜单id
        List<Long> menuIdList = roleMenuService.selectMenuIdByRoleId(id);
        // 4、根据顶级节点生成树形结构
        List<MenuTreeVO> tree = getMenuTree(listMap, BaseConst.TOP_LEVEL_NODE_ID,menuIdList);
        // 5、封装数据
        MenuVO allVo = new MenuVO();
        allVo.setMenuTreeVoList(tree);
        return allVo;
    }


    /**
     * 通过菜单id查询菜单详情信息
     *
     * @param id 菜单id
     * @return 菜单详情信息
     */
    public MenuTreeVO getMenuById(Long id) {
        // 1、查询菜单+校验菜单是否存在+封装菜单状态显示名称
        MenuTreeVO vo = menuService.selectMenuById(id);
        if (StringUtils.isNull(vo)) {
            throw new BusinessException(Errors.System.SYSTEM_01017);
        }
        // 2、设置父菜单名称
        Menu menuByParentId = menuService.getById(vo.getParentId());
        if (StringUtils.isNotNull(menuByParentId)){
            vo.setParentName(menuByParentId.getMenuName());
        }else if (BaseConst.TOP_LEVEL_NODE_ID.equals(vo.getParentId())){
            vo.setParentName(BaseConst.TOP_LEVEL_NODE_NAME);
        }
        vo.setStatusName(StatusEnum.getByName(vo.getStatus()).getName());
        // 3、返回数据
        return vo;
    }

    /**
     * 新增菜单信息
     *
     * @param reqDto 菜单信息
     * @return 执行结果
     */
    @Transactional(rollbackFor = Exception.class)
    public Boolean saveMenu(MenuAddReqDTO reqDto) {
        // 1、数据校验
        // 1.1 父菜单已停用，不允许新增
        Menu parentMenu = menuService.getById(reqDto.getParentId());
        if (!BaseConst.TOP_LEVEL_NODE_ID.equals(reqDto.getParentId()) && StatusEnum.DISABLE.getValue().equals(parentMenu.getStatus())) {
            throw new BusinessException(Errors.System.SYSTEM_01018);
        }
        // 1.2 当前父菜单下菜单名称唯一
        Menu deptByName = menuService.selectMenuByName(reqDto.getMenuName(), reqDto.getParentId());
        if (StringUtils.isNotNull(deptByName)) {
            // 菜单名称已存在
            throw new BusinessException(Errors.System.SYSTEM_01019);
        }
        // 2、保存菜单数据
        Menu menu = new Menu();
        BeanUtils.copyProperties(reqDto, menu);
        return menuService.save(menu);
    }

    /**
     * 更新菜单信息
     *
     * @param reqDto 菜单信息
     * @return 执行结果
     */
    @Transactional(rollbackFor = Exception.class)
    public Boolean updateMenu(MenuUpdateReqDTO reqDto) {
        // 1、数据校验
        // 1.1 父菜单已停用，不允许更新，且不是当前父菜单（父菜单有更新才校验）
        Menu parentMenu = menuService.getById(reqDto.getParentId());
        if (!BaseConst.TOP_LEVEL_NODE_ID.equals(reqDto.getParentId()) && !reqDto.getParentId().equals(parentMenu.getId()) && StatusEnum.DISABLE.getValue().equals(parentMenu.getStatus())) {
            throw new BusinessException(Errors.System.SYSTEM_01018);
        }
        // 1.2 当前父菜单下菜单名称唯一，且不是当前菜单
        Menu deptByName = menuService.selectMenuByName(reqDto.getMenuName(), reqDto.getParentId());
        if (StringUtils.isNotNull(deptByName) && !reqDto.getId().equals(deptByName.getId())) {
            throw new BusinessException(Errors.System.SYSTEM_01019);
        }
        // 1.3 上级菜单不能是自己
        if (reqDto.getParentId().equals(reqDto.getId())) {
            throw new BusinessException(Errors.System.SYSTEM_01020);
        }
        // 1.4 当前菜单下包含未停用的子菜单，当前菜单不能停用
        List<Menu> childrenMenuList = menuService.selectNormalChildrenMenuById(reqDto.getId());
        if (StatusEnum.DISABLE.getValue().equals(reqDto.getStatus()) && childrenMenuList.size() > 0) {
            throw new BusinessException(Errors.System.SYSTEM_01021);
        }
        // 2、根据id更新菜单信息
        Menu menu = new Menu();
        BeanUtils.copyProperties(reqDto, menu);
        return menuService.updateById(menu);
    }

    /**
     * 删除菜单信息
     *
     * @param id 菜单id
     * @return 执行结果
     */
    @Transactional(rollbackFor = Exception.class)
    public Boolean deleteMenu(Long id) {
        // 1、数据校验
        // 1.1 存在下级菜单,不允许删除
        List<Menu> childrenMenuList = menuService.selectChildrenMenuById(id);
        if (!CollectionUtils.isEmpty(childrenMenuList)) {
            throw new BusinessException(Errors.System.SYSTEM_01022);
        }
        // 1.2 菜单存在对应的角色,不允许删除
        List<Long> roleIdList = roleMenuService.selectRoleIdByMenuId(id);
        if (!CollectionUtils.isEmpty(roleIdList)) {
            throw new BusinessException(Errors.System.SYSTEM_01023);
        }
        // 2、删除菜单
        return menuService.removeById(id);
    }

    /**
     * 递归生成菜单树
     *
     * @param listMap 菜单列表数据
     * @param parentId 父菜单id
     * @param idList 被选中的菜单id集合
     * @return 菜单树
     */
    private List<MenuTreeVO> getMenuTree(Map<Long, List<Menu>> listMap, Long parentId,List<Long> idList) {
        // 初始化树结构列表
        List<MenuTreeVO> menuTreeVoList = new ArrayList<>();
        // 获取对于父主键下的下一级列表
        List<Menu> menuList = listMap.get(parentId);
        // 为空则是叶子节点了
        if (CollectionUtils.isEmpty(menuList)){
            return menuTreeVoList;
        }
        // 循环遍历列表
        for (Menu menu : menuList) {
            // 封装菜单数据到树结构节点对象
            MenuTreeVO vo = new MenuTreeVO();
            BeanUtils.copyProperties(menu, vo);
            vo.setStatusName(StatusEnum.getByName(menu.getStatus()).getName());
            if (idList.remove(menu.getId())){
                vo.setFlag(Boolean.TRUE);
            }
            // 递归调用，获取当前节点的所有子节点
            List<MenuTreeVO> menuTree = getMenuTree(listMap, menu.getId(),idList);
            vo.setChildren(menuTree);
            // 将当前节点添加到菜单树结构列表
                menuTreeVoList.add(vo);
        }
        // 返回菜单树结构列表
        return menuTreeVoList;
    }

    //    private List<MenuTreeVO> getMenuTree(List<Menu> menuList, Long parentId,List<Long> idList) {
//        // 初始化树结构列表
//        List<MenuTreeVO> menuTreeVoList = new ArrayList<>();
//        // 循环遍历列表
//        for (Menu menu : menuList) {
//            // 找到具有传入父节点ID的所有子节点
//            if (parentId.equals(menu.getParentId())) {
//                // 封装菜单数据到树结构节点对象
//                MenuTreeVO vo = new MenuTreeVO();
//                BeanUtils.copyProperties(menu, vo);
//                vo.setStatusName(StatusEnum.getByName(menu.getStatus()).getName());
//                if (idList.remove(menu.getId())){
//                    vo.setFlag(Boolean.TRUE);
//                }
//                // 递归调用，获取当前节点的所有子节点
//                List<MenuTreeVO> menuTree = getMenuTree(menuList, menu.getId(),idList);
//                vo.setChildren(menuTree);
//                // 将当前节点添加到菜单树结构列表
//                menuTreeVoList.add(vo);
//            }
//        }
//        // 返回菜单树结构列表
//        return menuTreeVoList;
//    }

}
